/**
 * Copyright 2011 Adrian Witas
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.abstractmeta.commandify.core;

import org.abstractmeta.commandify.Commandify;
import org.abstractmeta.commandify.OwnerSelfCommandifyAccessor;
import org.abstractmeta.commandify.command.*;
import org.abstractmeta.commandify.core.command.AncestorCommand;
import org.abstractmeta.reflectify.Accessor;
import org.abstractmeta.reflectify.Reflectify;

import java.util.*;

/**
 * Represents CommandsImpl
 *
 * @author Adrian Witas
 */
public class CommandsImpl<T> implements Commands<T> {

    private final Class<T> ownerType;
    private final Collection<Command<T>> commands;
    //private final Map<Object, Commandify> commandifyRegistry;

    private ConstructorCommand<T> constructorCommand;
    private Reflectify<T> reflectify;
    private OwnerSelfCommandifyAccessor<Object, T> ownerSelfAccessor;
    private String uniqueField;

    public CommandsImpl(Class<T> ownerType) {
        this(ownerType, new LinkedList<Command<T>>());
    }

    public CommandsImpl(Class<T> ownerType, Collection<Command<T>> commands) {
        this.ownerType = ownerType;
        this.commands = commands;
     }


    protected void setCommandifyAccessor(OwnerSelfCommandifyAccessor<Object, T> ownerSelfCommandifyAccessor) {
        this.ownerSelfAccessor = ownerSelfCommandifyAccessor;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void associateOwnerSelfCommandAccessor(Object owner, Object source) {
        if (owner instanceof Commandify) {
            Commandify commandifyOwner = (Commandify) owner;
            OwnerSelfCommandifyAccessor ancestorAccessor = commandifyOwner.getCommandifyAccessor(source);
            setCommandifyAccessor(ancestorAccessor);
        }
    }

    @Override
    public void dissociateOwnerSelfCommandAccessor() {
        ownerSelfAccessor = null;
    }


    @Override
    public OwnerSelfCommandifyAccessor<Object, T> getCommandifyAccessor() {
        return ownerSelfAccessor;
    }

    @Override
    public void setReflectify(Reflectify<T> reflectify) {
        this.reflectify = reflectify;
    }

    @Override
    public Reflectify<T> getReflectify() {
        return reflectify;
    }

    @Override
    public Accessor<T, Object> getAccessor(Object source) {
        return reflectify.getAccessor(Object.class, String.valueOf(source));
    }

    @Override
    public void setUniqueField(String fieldName) {
        this.uniqueField = fieldName;
    }

    @Override
    public String getUniqueField() {
        return uniqueField;
    }


    @Override
    public boolean contains(String source) {
        for (Command<T> command : commands) {
            if (source.equals(command.getSource())) {
                return true;
            }
        }
        return false;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void add(Command<T> command) {
        if (command instanceof FieldCommand) {
            FieldCommand fieldCommand = FieldCommand.class.cast(command);
            Object value = fieldCommand.getValue();
            if (!hasChange(value, fieldCommand.getPreviousValue())) {
                return;
            }
            notifyChange(fieldCommand.getSource(), value, fieldCommand.getPreviousValue(), command);

        }

        if (isAncestorMangedCommand(this)) {
            notifyAncestor(this, command);
        } else {
            this.commands.add(command);
        }
    }


    protected boolean isAncestorMangedCommand(Commands commands) {
        return commands.getCommandifyAccessor() != null;
    }


    @SuppressWarnings("unchecked")
    protected void notifyChange(Object source, Object value, Object previousValue, Command<T> command) {
//        if (value != null && (value instanceof Commandify)) {
//            Commandify valueCommandify = (Commandify) value;
//            commandifyRegistry.put(source, valueCommandify);
//        }
        if (value == null && previousValue != null && previousValue instanceof Commandify) {
            Commandify.class.cast(previousValue).getCommands().dissociateOwnerSelfCommandAccessor();
        }
    }


    @SuppressWarnings("unchecked")
    /**
     * Notifies
     */
    protected void notifyAncestor(Commands commands, Command<T> command) {
        OwnerSelfCommandifyAccessor ancestorAccessor = commands.getCommandifyAccessor();
        Commandify commandifyOwner = ancestorAccessor.getOwner();
        AncestorCommand ancestorCommand = new AncestorCommand(ancestorAccessor, command);
        commandifyOwner.getCommands().add(ancestorCommand);
    }

    protected boolean hasChange(Object value, Object previousValue) {
        return !(value != null && value.equals(previousValue));
    }


    @Override
    public Class<T> getOwnerType() {
        return ownerType;
    }


    @Override
    public ConstructorCommand<T> getConstructorCommand() {
        return constructorCommand;
    }

    @Override
    public void setConstructorCommand(ConstructorCommand<T> command) {
        this.constructorCommand = command;
    }


    @Override
    @SuppressWarnings("unchecked")
    public T apply() {
        if (constructorCommand == null) {
            throw new IllegalStateException("constructorCommand was null");
        }
        T result = constructorCommand.execute();
        apply(result);
        return result;
    }


    @Override
    public void apply(T instance) {
        for (Command<T> command : commands) {
            command.execute(instance);
        }

    }

    @Override
    public void apply(String source, T instance) {
        for (Command<T> command : commands) {
            if (!source.equals(command.getSource())) continue;
            command.execute(instance);
        }
    }

    @Override
    public int size() {
        return commands.size();
    }

    @Override
    public void clear() {
        commands.clear();
//        for (Commandify commandify : commandifyRegistry.values()) {
//            commandify.getCommands().clear();
//        }
    }


    @Override
    public Iterator<Command<T>> iterator() {
        return commands.iterator();
    }

}
